﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace Hont
{
    public class BalanceableTaskScheduler
    {
        public struct TaskInfo
        {
            public int? ID { get; set; }
            public Action Action { get; set; }
        }

        struct TaskGroup
        {
            public List<TaskInfo> TaskList { get; set; }
            public float WeightCoefficient { get; set; }

            public float InternalAverageWeight { get; set; }
            public float InternalCounter { get; set; }

            public int ID { get { return TaskList.GetHashCode(); } }
            public bool IsInvalid { get { return TaskList == null; } }
        }

        float mTaskTimeCounterOffsetCoefficient;
        float mMinimumTimeUnit;
        List<TaskGroup> mTaskGroupList;

        /// <summary>
        /// 任务计数器偏移系数(若2个任务组都是0.5的权值，则他们会在同一刻执行任务。通过偏移系数来修改他们的初始时间点，从而避免这个问题)。
        /// </summary>
        public float TaskTimeCounterOffsetCoefficient { get { return mTaskTimeCounterOffsetCoefficient; } set { mTaskTimeCounterOffsetCoefficient = value; } }


        /// <summary>
        /// </summary>
        /// <param name="minimumTimeUnit">最小时间单位，每个最小时间单位执行一次任务检测。</param>
        public BalanceableTaskScheduler(float minimumTimeUnit = 0.05f)
        {
            mTaskGroupList = new List<TaskGroup>();
            mMinimumTimeUnit = minimumTimeUnit;
            mTaskTimeCounterOffsetCoefficient = mMinimumTimeUnit * 0.2f;
        }

        /// <summary>
        /// 注册任务组(每次任务组产生变更都会重计算平均权重，权重修改后执行速率也会改变)。
        /// </summary>
        /// <param name="weightCoefficient">权重系数，默认为1。假设有两个任务组，他们都设置系数为1，平均权重则各为0.5。</param>
        /// <param name="taskListCapacity">任务列表的缓存大小</param>
        /// <returns>任务组的ID</returns>
        public int RegistTaskGroup(float weightCoefficient = 1f, int taskListCapacity = 10)
        {
            var result = -1;
            var taskGroup = new TaskGroup() { TaskList = new List<TaskInfo>(taskListCapacity), WeightCoefficient = weightCoefficient };
            result = taskGroup.ID;

            mTaskGroupList.Add(taskGroup);

            TaskGroupDetection();

            return result;
        }

        /// <summary>
        /// 反注册任务组(每次任务组产生变更都会重计算平均权重，权重修改后执行速率也会改变)。
        /// </summary>
        /// <param name="id">任务组ID</param>
        /// <returns>若不存在该任务组ID则返回False否则返回True</returns>
        public bool UnregistTaskGroup(int id)
        {
            var count = mTaskGroupList.RemoveAll(m => m.ID == id);

            TaskGroupDetection();

            return count > 0;
        }

        /// <summary>
        /// 添加任务到任务组。
        /// </summary>
        /// <param name="taskGroupID">任务组ID</param>
        /// <param name="task">任务函数</param>
        /// <param name="taskID">任务ID</param>
        /// <returns>若不存在任务组则添加失败返回False否则返回True</returns>
        public bool AddToTaskGroup(int taskGroupID, Action task, int? taskID = null)
        {
            var result = false;
            var targetGroup = SearchTaskGroup(taskGroupID);

            if (!targetGroup.IsInvalid)
            {
                targetGroup.TaskList.Add(new TaskInfo() { Action = task, ID = taskID });
                result = true;
            }

            return result;
        }

        /// <summary>
        /// 添加任务到任务组。
        /// </summary>
        /// <param name="taskGroupID">任务组ID</param>
        /// <param name="tasks">任务数组</param>
        /// <returns>若不存在任务组则添加失败返回False否则返回True</returns>
        public bool AddToTaskGroup(int taskGroupID, IList<TaskInfo> tasks)
        {
            var result = false;
            var targetGroup = SearchTaskGroup(taskGroupID);

            if (!targetGroup.IsInvalid)
            {
                for (int i = 0, iMax = tasks.Count; i < iMax; i++)
                {
                    targetGroup.TaskList.Add(tasks[i]);
                }
                result = true;
            }

            return result;
        }

        /// <summary>
        /// 获得目标任务组中的任务数量。
        /// </summary>
        /// <param name="taskGroupID">目标任务组ID</param>
        /// <returns>若不存在该任务组ID则返回-1否则返回具体数目</returns>
        public int GetTasksCount(int taskGroupID)
        {
            var result = -1;
            var targetGroup = SearchTaskGroup(taskGroupID);

            if (!targetGroup.IsInvalid)
                result = targetGroup.TaskList.Count;

            return result;
        }

        /// <summary>
        /// 立即执行目标任务组中的指定任务。
        /// </summary>
        /// <param name="taskGroupID">目标任务组ID</param>
        /// <param name="taskID">指定任务的ID</param>
        /// <returns>若不存在目标任务组或指定任务则返回False否则返回True</returns>
        public bool ImmediateExecuteSpecifiedTask(int taskGroupID, int taskID)
        {
            var result = false;
            var targetGroup = SearchTaskGroup(taskGroupID);

            if (!targetGroup.IsInvalid)
            {
                var taskIndex = SearchTaskIndex(targetGroup, taskID);

                if (taskIndex > -1)
                {
                    var item = targetGroup.TaskList[taskIndex];
                    item.Action();
                    result = true;
                }
            }

            return result;
        }

        /// <summary>
        /// 立即移除目标任务组中的指定任务。
        /// </summary>
        /// <param name="taskGroupID">目标任务组ID</param>
        /// <param name="taskID">指定任务的ID</param>
        /// <returns>若不存在目标任务组或指定任务则返回False否则返回True</returns>
        public bool ImmediateRemoveSpecifiedTask(int taskGroupID, int taskID)
        {
            var result = false;
            var targetGroup = SearchTaskGroup(taskGroupID);

            if (!targetGroup.IsInvalid)
            {
                var taskIndex = SearchTaskIndex(targetGroup, taskID);

                if (taskIndex > -1)
                {
                    targetGroup.TaskList.RemoveAt(taskIndex);
                    result = true;
                }
            }

            return result;
        }

        /// <summary>
        /// 立即执行目标任务组中的所有剩余任务。
        /// </summary>
        /// <param name="taskGroupID">目标任务组ID</param>
        /// <returns>若不存在任务组则添加失败返回False否则返回True</returns>
        public bool ImmediateExecuteRemainTasks(int taskGroupID)
        {
            var result = false;
            var targetGroup = SearchTaskGroup(taskGroupID);

            if (!targetGroup.IsInvalid)
            {
                for (int i = 0, iMax = targetGroup.TaskList.Count; i < iMax; i++)
                {
                    var item = targetGroup.TaskList[i];
                    item.Action();
                }
                targetGroup.TaskList.Clear();

                result = true;
            }

            return result;
        }

        /// <summary>
        /// 更新任务调度器(需创建方主动调用)。
        /// </summary>
        public void Update(float deltaTime)
        {
            for (int i = 0, iMax = mTaskGroupList.Count; i < iMax; i++)
            {
                var item = mTaskGroupList[i];

                ExecuteTask(ref item, deltaTime);

                mTaskGroupList[i] = item;
            }
        }

        /// <summary>
        /// 改变最小时间单位(每个最小时间单位执行一次任务检测)，注:修改该值会导致即将执行的任务延迟触发。
        /// </summary>
        /// <param name="newValue">新的最小时间单位值</param>
        public void ChangeMinimumTimeUnit(float newValue)
        {
            mMinimumTimeUnit = newValue;
        }

        void TaskGroupDetection()
        {
            #region ---  Process Average Weight ---
            var sum = 0f;
            for (int i = 0, iMax = mTaskGroupList.Count; i < iMax; i++)
                sum += mTaskGroupList[i].WeightCoefficient;

            for (int i = 0, iMax = mTaskGroupList.Count; i < iMax; i++)
            {
                var item = mTaskGroupList[i];

                item.InternalAverageWeight = item.WeightCoefficient / sum;

                mTaskGroupList[i] = item;
            }
            #endregion

            for (int i = 0, iMax = mTaskGroupList.Count; i < iMax; i++)
            {
                var item = mTaskGroupList[i];

                item.InternalCounter += Mathf.Repeat(i * mTaskTimeCounterOffsetCoefficient, mMinimumTimeUnit);

                mTaskGroupList[i] = item;
            }
        }

        void ExecuteTask(ref TaskGroup taskGroup, float deltaTime)
        {
            if (taskGroup.TaskList.Count == 0) return;

            taskGroup.InternalCounter += taskGroup.InternalAverageWeight * deltaTime;

            if (taskGroup.InternalCounter >= mMinimumTimeUnit)
            {
                var flag = false;
                var error = 0;

                do
                {
                    if (error > 10000)
                        throw new System.StackOverflowException();
                    error++;

                    var task = taskGroup.TaskList[0];
                    task.Action();
                    taskGroup.TaskList.RemoveAt(0);
                    taskGroup.InternalCounter -= mMinimumTimeUnit;

                    flag = taskGroup.TaskList.Count > 0 && taskGroup.InternalCounter >= mMinimumTimeUnit;
                } while (flag);
            }
        }

        TaskGroup SearchTaskGroup(int taskGroupID)
        {
            var targetGroup = default(TaskGroup);

            for (int i = 0, iMax = mTaskGroupList.Count; i < iMax; i++)
            {
                var item = mTaskGroupList[i];

                if (item.ID == taskGroupID)
                {
                    targetGroup = item;
                    break;
                }
            }

            return targetGroup;
        }

        int SearchTaskIndex(TaskGroup taskGroup, int taskID)
        {
            int result = -1;

            for (int i = 0, iMax = taskGroup.TaskList.Count; i < iMax; i++)
            {
                var item = taskGroup.TaskList[i];

                if (item.ID != null && item.ID.Value == taskID)
                {
                    result = i;
                    break;
                }
            }

            return result;
        }
    }
}
